java琐碎知识点学习

开发规范:

  • post请求写json格式请求示例包括header
  • 修改操作暴露的字段只有允许修改的字段,其他字段不允许暴露
  • 接口层面传输不应该暴露id,但是底层逻辑要存id,哪怕需要多查询一次
  • 数据库在第一版上线后,所有的变化都需要记录sql语句,后面版本迭代后直接上线sql语句即可
  • post请求都需要用新的vo去接收,不能用实体类映射的vo去接收
  • 接口文档只暴露在页面上需要的接口
  • 所有业务逻辑必须考虑所有可能存在的特殊情况
  • 所有数据库中的敏感字段在响应中必须修改一下,比如数据库存的status,就改成type:xxx
  • 考虑所有接口参数都不规范的前提下去判断参数
  • 不使用多表查询
  • 分页接口pageNum,pageSize设定默认值
  • 消息队列发送后,在消息消费过程中出现异常处理:第一次执行,报错,捕获,重试,第二次执行,报错,捕获记录错误日志到数据库,确认消费
  • 增删改牵扯到多表关系或者关键表操作需要增加事务注解
  • 直接操作数据库修改数据时,要么把关联的数据全清了,要么就在接口层面操作,否则脏数据问题很难排查
  • 接口层面传输能不用id不用id
  • 方法层面传输能用id就用id
  • 非主要服务类方法可以加@Async变成异步
    • 在springboot中可能会出现循环依赖的问题,在出现问题的注入类上加上@Lazy即可
  • django中的CBV或者springboot的service尽量遵循单一职责
  • 代码可读性的体现在于注释,在于细节,减少没必要的查询,规范命名
  • 数据库设计中,随机性很高的字段用

1 @Controller和@RestController的区别?

@RestController = @Controller + @ResponseBody

1.1 @Controller

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
    @AliasFor(
        annotation = Component.class
    )
    String value() default "";
}

在控制器类前添加这个注解后,返回的信息必须配合模板语言来使用

类似Django的rander

1.2 @RestController

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    @AliasFor(
        annotation = Controller.class
    )
    String value() default "";
}

使用**@RestController注解后可以让一个类内的所有方法的返回的对象通过适当的转换器转为指定的格式之后放入response.body**中,他的效果等同于通过response对象输出指定格式的数据。

问题列表:

  • 什么是AOP和IOC
  • springboot框架的请求上下文全流程

2 控制器报错

在对应的impl类上加上注解@Service

image-20210312145103696

3 java连接redis

在spring中包含了redisTemplate(对redis操作)

配置方式:

pom文件


        <!-- spring boot 配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

application.properties

# Redis数据库索引(默认为0)
spring.redis.database=0  
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379  
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8  
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1  
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8  
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0  
# 连接超时时间(毫秒)
spring.redis.timeout=0  

创建一个redis配置

创建redis工具类

https://www.cnblogs.com/superfj/p/9232482.html

4 transient关键字

java的serialization提供了一个非常棒的存储对象状态的机制,说白了serialization就是把对象的状态存储到硬盘上 去,等需要的时候就可以再把它读出来使用。有些时候像银行卡号这些字段是不希望在网络上传输的,transient的作用就是把这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化,意思是transient修饰的age字段,他的生命周期仅仅在内存中,不会被写到磁盘中。

使用场景

(1)类中的字段值可以根据其它字段推导出来,如一个长方形类有三个属性长度、宽度、面积,面积不需要序列化。

(2)一些安全性的信息,一般情况下是不能离开JVM的。

(3)如果类中使用了Logger实例,那么Logger实例也是不需要序列化的

5 lombok

https://www.jianshu.com/p/422f151fccd3

使用:idea中安装lombok插件,maven

常用注解

@Accessors(chain = true)

@Accessors(chain = true)
// 通常加在javabean上,可以链式调用

public class AppTest {
    public static void main(String[] args) {
        User user = new User();
        User hz = user.setName("hz").setPwd("123");
    }

}

@Data
@Accessors(chain = true)
class User{
    private String name;
    private String pwd;
}

源码

@Target({ElementType.TYPE, ElementType.FIELD}) // 作用场景:类,接口,枚举,字段
@Retention(RetentionPolicy.SOURCE)
public @interface Accessors {
    boolean fluent() default false;

    boolean chain() default false;  // chain = true 即可开启链式调用

    String[] prefix() default {};
}

@Cleanup

可以自动释放资源,比如在redis连接报错后,需要捕获异常并关闭连接,就可以用到

public void jedisExample(String[] args) {
    try {
        @Cleanup Jedis jedis =   redisService.getJedis();
    } catch (Exception ex) {
        logger.error(“Jedis异常:,ex)
    }
}

效果相当于

public void jedisExample(String[] args) {

    Jedis jedis= null;
    try {
        jedis = redisService.getJedis();
    } catch (Exception e) {
        logger.error(“Jedis异常:,ex)
    } finally {
        if (jedis != null) {
            try {
                jedis.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

@Getter/@Setter

注解可以针对类的属性字段自动生成Get/Set方法

@ToString

为该注解下的类自动创建toString方法

@ToString(callSuper=true,exclude="someExcludedField")
public   class Demo extends Bar {

    private boolean someBoolean = true;
    private String someStringField;
    private float someExcludedField;

}

//上面代码相当于如下:

public   class Demo extends Bar {

    private boolean someBoolean = true;
    private String someStringField;
    private float someExcludedField;

    @ Override
    public String toString() {
        return "Foo(super=" +   super.toString() +
            ", someBoolean=" +   someBoolean +
            ", someStringField=" +   someStringField + ")";
    }
}

@Data

@Data最常用的注解之一。注解在类上,提供该类所有属性的getter/setter方法,还提供了equals、canEqual、hashCode、toString方法。

编译后效果

public class Demo {
    private int id;
    private String remark;

    public Demo() {
    }

    public int getId() {
        return this.id;
    }

    public String getRemark() {
        return this.remark;
    }

    public void setId(final int id) {
        this.id = id;
    }

    public void setRemark(final String remark) {
        this.remark = remark;
    }

    public boolean equals(final Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof Demo)) {
            return false;
        } else {
            Demo other = (Demo)o;
            if (!other.canEqual(this)) {
                return false;
            } else if (this.getId() != other.getId()) {
                return false;
            } else {
                Object this$remark = this.getRemark();
                Object other$remark = other.getRemark();
                if (this$remark == null) {
                    if (other$remark != null) {
                        return false;
                    }
                } else if (!this$remark.equals(other$remark)) {
                    return false;
                }

                return true;
            }
        }
    }

    protected boolean canEqual(final Object other) {
        return other instanceof Demo;
    }

    public int hashCode() {
        int PRIME = true;
        int result = 1;
        int result = result * 59 + this.getId();
        Object $remark = this.getRemark();
        result = result * 59 + ($remark == null ? 43 : $remark.hashCode());
        return result;
    }

    public String toString() {
        return "Demo(id=" + this.getId() + ", remark=" + this.getRemark() + ")";
    }
}

@AllArgsConstructor

为一个类体统包含全部属性为参数的构造器

6 类型转换

String与jsonobject

// Hashmap -> json
Map<String, Object> data =new HashMap<>();
String x =JSONObject.toJSONString(data);

String与map

import com.alibaba.fastjson;
String str="";

// String -> map
// 方式1
HashMap hashMap = JSON.parseObject(str, HashMap.class);
// 方式2
Map maps = (Map) JSON.parse(str);
// String -> 自定义对象
Product product = JSONObject.parseObject(str, Product.class);

// map -> Json String
String data = JSON.toJSONString(map);

Map<String, Object> data =new HashMap<>();
String x =JSONObject.toJSONString(data);

string与list

String str1 = StringUtils.join(list, ","); //a,b,c

List<String> list1 = Arrays.asList(str.split(",")); //[a, b, c]

// json -> list
List<ExamListInVO> list = new ArrayList<ExamListInVO>();
list = JSONObject.parseArray(strResult, ExamListInVO.class);

map与jsonobject

public static final Object parse(String text); // 把JSON文本parse为JSONObject或者JSONArray 
public static final JSONObject parseObject(String text)// 把JSON文本parse成JSONObject    
public static final  T parseObject(String text, Class clazz); // 把JSON文本parse为JavaBean 
public static final JSONArray parseArray(String text); // 把JSON文本parse成JSONArray 
public static final  List parseArray(String text, Class clazz); //把JSON文本parse成JavaBean集合 
public static final String toJSONString(Object object); // 将JavaBean序列化为JSON文本 
public static final String toJSONString(Object object, boolean prettyFormat); // 将JavaBean序列化为带格式的JSON文本 
public static final Object toJSON(Object javaObject); 将JavaBean转换为JSONObject或者JSONArray。

7 queryWrapper常用方法

在这里插入图片描述

8 订单支付超时自动取消订单解决方案(延时队列)

http://www.dockone.io/article/10139

  • jdk中的DelayQueue
  • Quartz定时任务
  • redis sorted set
  • redis过期回调
  • rabbitmq延时队列
  • kafka,netty的时间轮

9 时间模块

获取当前时间戳

//方法 一
System.currentTimeMillis();
//方法 二 效率最慢
Calendar.getInstance().getTimeInMillis();
//方法 三
new Date().getTime();

// 获取今天时间
System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
// 获取昨天时间
System.out.println(new SimpleDateFormat("yyyy-MM-dd").format(new Date(System.currentTimeMillis() - 1000 * 60 * 60 * 24)));

获取当前格式化时间

SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
String date = df.format(new Date());// new Date()为获取当前系统时间,也可使用当前时间戳

Date date1 = new Date();
System.out.println(date1.getTime()); // 1618456775789
System.out.println(date1); // Thu Apr 15 11:19:35 CST 2021
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
String date = df.format(new Date());
System.out.println(date); // 2021-04-15 11:19:35

10 rabbitmq

AMQP和JMS

MQ是消息通信的模型,并发具体实现。现在实现MQ的有两种主流方式:AMQP、JMS。

两者间的区别和联系:

  • JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
  • JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
  • JMS规定了两种消息模型;而AMQP的消息模型更加丰富

常见MQ产品

  • ActiveMQ:基于JMS

  • RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好

  • RocketMQ:基于JMS,阿里巴巴产品,目前交由Apache基金会
  • Kafka:分布式消息系统,高吞吐量

基本使用

工作原理

img

组成部分说明:

Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue
Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。
Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的
Producer:消息生产者,即生产方客户端,生产方客户端将消息发送
Consumer:消息消费者,即消费方客户端,接收MQ转发的消息。
生产者发送消息流程:

1、生产者和Broker建立TCP连接。

2、生产者和Broker建立通道。

3、生产者通过通道消息发送给Broker,由Exchange将消息进行转发。

4、Exchange将消息转发到指定的Queue(队列)

消费者接收消息流程:

1、消费者和Broker建立TCP连接

2、消费者和Broker建立通道

3、消费者监听指定的Queue(队列)

4、当有消息到达Queue时Broker默认将消息推送给消费者。

5、消费者接收到消息。

6、ack回复

写demo的时候注意点

  • 连接时所需的用户需提前创建
  • 连接虚拟机时需给用户对这个虚拟机的权限
  • connection error; protocol method: #method<connection.close>(reply-code=530, reply-text=NOT_ALLOWED 当出现这个报错就是这个用户没有对这个虚拟机的权限
1、安装

brew install rabbitmq

2、启动及关闭RabbitMQ服务

前台启动 

sudo ./rabbitmq-server    或

sudo su
/usr/local/Cellar/rabbitmq/3.7.8/sbin/rabbitmq-server -detacted

后台启动 sudo ./rabbitmq-server -detached

后台关闭 sudo ./rabbitmqctl stop

3 、登录
http://127.0.0.1:15672    guest\guest

4、创建用户与虚拟机并授权
rabbitmqctl add_user USER PASSWORD ##创建用户
rabbitmqctl change_password USER PASSWORD ##修改密码
rabbitmqctl set_user_tags USER administrator  ##设置为管理员
rabbitmqctl add_vhost VHOST ##添加虚拟机
rabbitmqctl set_permissions -p VHOST USER ".*" ".*" ".*"  ##给用户分配虚拟主机权限

创建连接

package com.hz.mq;

import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class rabbitmqUtil {
    /**
     * 建立与RabbitMQ的连接
     *
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception {
        //定义连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //设置服务地址
        factory.setHost("127.0.0.1");
        //端口
        factory.setPort(5672);
        //设置账号信息,用户名、密码、vhost
        factory.setVirtualHost("mqv");//设置虚拟机,一个mq服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的mq
        factory.setUsername("hz");
        factory.setPassword("123456");
        // 通过工厂获取连接
        Connection connection = factory.newConnection();
        return connection;
    }

}

生产者

package com.hz.mq;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class MqProducer {


    private final static String QUEUE_NAME = "simple_queue";

    public static void main(String[] argv) throws Exception {
        // 1、获取到连接
        Connection connection = rabbitmqUtil.getConnection();
        // 2、从连接中创建通道,使用通道才能完成消息相关的操作
        Channel channel = connection.createChannel();
        // 3、声明(创建)队列
        //参数:String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
        /**
         * 参数明细
         * 1、queue 队列名称
         * 2、durable 是否持久化,如果持久化,mq重启后队列还在
         * 3、exclusive 是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列则自动删除,如果将此参数设置true可用于临时队列的创建
         * 4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除)
         * 5、arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间
         */
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 4、消息内容
        String message = "Hello World!";
        // 向指定的队列中发送消息
        //参数:String exchange, String routingKey, BasicProperties props, byte[] body
        /**
         * 参数明细:
         * 1、exchange,交换机,如果不指定将使用mq的默认交换机(设置为"")
         * 2、routingKey,路由key,交换机根据路由key来将消息转发到指定的队列,如果使用默认交换机,routingKey设置为队列的名称
         * 3、props,消息的属性
         * 4、body,消息内容
         */
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");

        //关闭通道和连接(资源关闭最好用try-catch-finally语句处理)
        channel.close();
        connection.close();
    }

}

消费者

package com.hz.mq;

import com.rabbitmq.client.*;

import java.io.IOException;

public class MqConsumer {
    private final static String QUEUE_NAME = "simple_queue";

    public static void main(String[] argv) throws Exception {
        // 获取到连接
        Connection connection = rabbitmqUtil.getConnection();
        //创建会话通道,生产者和mq服务所有通信都在channel通道中完成
        Channel channel = connection.createChannel();
        // 声明队列
        //参数:String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
        /**
         * 参数明细
         * 1、queue 队列名称
         * 2、durable 是否持久化,如果持久化,mq重启后队列还在
         * 3、exclusive 是否独占连接,队列只允许在该连接中访问,如果connection连接关闭队列则自动删除,如果将此参数设置true可用于临时队列的创建
         * 4、autoDelete 自动删除,队列不再使用时是否自动删除此队列,如果将此参数和exclusive参数设置为true就可以实现临时队列(队列不用了就自动删除)
         * 5、arguments 参数,可以设置一个队列的扩展参数,比如:可设置存活时间
         */
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        //实现消费方法
        DefaultConsumer consumer = new DefaultConsumer(channel){
            // 获取消息,并且处理,这个方法类似事件监听,如果有消息的时候,会被自动调用
            /**
             * 当接收到消息后此方法将被调用
             * @param consumerTag  消费者标签,用来标识消费者的,在监听队列时设置channel.basicConsume
             * @param envelope 信封,通过envelope
             * @param properties 消息属性
             * @param body 消息内容
             * @throws IOException
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //交换机
                String exchange = envelope.getExchange();
                //消息id,mq在channel中用来标识消息的id,可用于确认消息已接收
                long deliveryTag = envelope.getDeliveryTag();
                // body 即消息体
                String msg = new String(body,"utf-8");
                System.out.println(" [x] received : " + msg + "!");
            }
        };

        // 监听队列,第二个参数:是否自动进行消息确认。
        //参数:String queue, boolean autoAck, Consumer callback
        /**
         * 参数明细:
         * 1、queue 队列名称
         * 2、autoAck 自动回复,当消费者接收到消息后要告诉mq消息已接收,如果将此参数设置为tru表示会自动回复mq,如果设置为false要通过编程实现回复
         * 3、callback,消费方法,当消费者接收到消息要执行的方法
         */
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }


}

ack确认机制

自动ack会在处理消息过程中即使出现异常,也会消息确认

在消息重要的情况下切换手动ack,手动ack后要主动发送ack,否在消息状态为未确认(unacked),在关闭消费者后,状态会回退到ready

消息队列发送后,在消息消费过程中出现异常处理:第一次执行,报错,捕获,重试,第二次执行,报错,捕获记录错误日志到数据库,确认消费

配置direct交换机(直连交换机)

package com.dq.config.mq;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 定义队列名和交换机
 */
@Configuration
public class DirectMqConfig {

    /**
     * 交换机名称
     */
    public static final String DIRECT_EXCHANGE_NAME = "direct_exchange";

    /**
     * 绑定key,交换机绑定队列时需要指定
     */
    public static final String BINGDING_KEY_TEST1 = "direct_key1";
    public static final String BINGDING_KEY_TEST2 = "direct_key2";
    public static final String BINGDING_KEY_TEST3 = "direct_key3";

    /**
     * 队列名称
     */
    public static final String QUEUE_TEST1 = "addProductUsderBid";
    public static final String QUEUE_TEST2 = "updateProduct1";
    public static final String QUEUE_TEST3 = "updateStateProduct1";

    /**
     * 构建DirectExchange交换机
     *
     * @return
     */
    @Bean
    public DirectExchange directExchange() {
        // 支持持久化,长期不用补删除
        return new DirectExchange(DIRECT_EXCHANGE_NAME, true, false);
    }

    /**
     * 构建序列
     *
     * @return
     */
    @Bean
    public Queue test1Queue() {
        // 支持持久化
        return new Queue(QUEUE_TEST1, true);
    }

    @Bean
    public Queue test2Queue() {
        // 支持持久化
        return new Queue(QUEUE_TEST2, true);
    }

    @Bean
    public Queue test3Queue() {
        // 支持持久化
        return new Queue(QUEUE_TEST3, true);
    }

    /**
     * 绑定交交换机和
     *
     * @return
     */
    @Bean
    public Binding test1Binding() {
        return BindingBuilder.bind(test1Queue()).to(directExchange()).with(BINGDING_KEY_TEST1);
    }

    @Bean
    public Binding test2Binding() {
        return BindingBuilder.bind(test2Queue()).to(directExchange()).with(BINGDING_KEY_TEST2);
    }

    @Bean
    public Binding test3Binding() {
        return BindingBuilder.bind(test3Queue()).to(directExchange()).with(BINGDING_KEY_TEST3);
    }

    /**
     * 实例化操作模板
     *
     * @param connectionFactory
     * @return
     */
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        //必须为true,否则无法触发returnedMessage回调,消息丢失
        rabbitTemplate.setMandatory(true);
        return rabbitTemplate;
    }

}

消息分发与能者多劳

当出现两个消费者时,消费者2处理消息的速度远小于消费者1的情况下,他们会受到同样数量的消息

此时消费者1有大量时间处于空闲状态,可以通过 BasicQos 方法设置prefetchCount = 1

这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理1个Message。换句话说,在接收到该Consumer的ack前,他它不会将新的Message分发给它。相反,它会将其分派给不是仍然忙碌的下一个Consumer。

值得注意的是:prefetchCount在手动ack的情况下才生效,自动ack不生效。

订阅模式

。。。

11 分布式id设计模式

基于数据库的号段模式

12 quartz定时任务时间

表达式 允许值
“0 0 12 * * ?” 每天中午12点触发
“0 15 10 ? * *” 每天上午10:15触发
“0 15 10 * * ?” 每天上午10:15触发
“0 15 10 * * ? *” 每天上午10:15触发
“0 15 10 * * ? 2005” 2005年的每天上午10:15触发
“0 * 14 * * ?” 在每天下午2点到下午2:59期间的每1分钟触发
“0 0/5 14 * * ?” 在每天下午2点到下午2:55期间的每5分钟触发
“0 0/5 14,18 * * ?” 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
“0 0-5 14 * * ?” 在每天下午2点到下午2:05期间的每1分钟触发
“0 10,44 14 ? 3 WED” 每年三月的星期三的下午2:10和2:44触发
“0 15 10 ? * MON-FRI” 周一至周五的上午10:15触发
“0 15 10 15 * ?” 每月15日上午10:15触发
“0 15 10 L * ?” 每月最后一日的上午10:15触发
“0 15 10 ? * 6L” 每月的最后一个星期五上午10:15触发
“0 15 10 ? * 6L 2002-2005” 2002年至2005年的每月的最后一个星期五上午10:15触发
“0 15 10 ? * 6#3” 每月的第三个星期五上午10:15触发
0 6 * * * 每天早上6点
0 /2 * * 每两个小时
0 23-7/2,8 * * * 晚上11点到早上8点之间每两个小时,早上八点
0 11 4 * 1-3 每个月的4号和每个礼拜的礼拜一到礼拜三的早上11点
0 4 1 1 * 1月1日早上4点

13 mysql设置默认更新时间

可以用下面的ALTER语句来修改create_time默认为当前时间、update_time更新时间为当前修改更新的时间

ALTER TABLE `user`
MODIFY COLUMN create_time datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间';

ALTER TABLE `user`
MODIFY COLUMN `update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间';